﻿using System;
using System.IO;
using System.IO.Packaging;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Xps;
using System.Windows.Xps.Packaging;
using System.Windows.Xps.Serialization;


namespace ThePresenter.IO
{
	public class Xps : AbstractFormat, IWriter, IReader
	{
		#region Constants
		/// <summary>
		/// Default background image resource key.
		/// </summary>
		const string BGIMG_KEY = "b0";

		/// <summary>
		/// Strokes resource key.
		/// </summary>
		const string STROKES_KEY = "STROKES";
		#endregion


		#region Fields
		/// <summary>
		/// The temporary package Uri for the package store, which manages packages.
		/// </summary>
		static readonly Uri PackageUri = new Uri("pack://TemporaryPackageUri.xps");
		#endregion


		#region IFormat Members
		/// <summary>
		/// Gets the filter that is shown in the <see cref="Microsoft.Win32.OpenFileDialog"/> and <see cref="Microsoft.Win32.SaveFileDialog"/> dialogs. If is null, this format does not support writing or reading to / from file. Can be null.
		/// </summary>
		/// <value>The filter shown in the dialogs.</value>
		public override string Filter
		{
			get
			{
				return "Microsoft XML Paper Specification (*.xps)|*.xps";
			}
		}
		#endregion


		#region IWriter Members
		/// <summary>
		/// Writes the specified presentation to the stream.
		/// </summary>
		/// <param name="config">The configuration object.</param>
		/// <param name="presentation">The presentation.</param>
		/// <param name="stream">The stream.</param>
		/// <exception cref="ThePresenter.WriteException"/>
		public void Write(DependencyObject config, Presentation presentation, Stream stream)
		{
			if (presentation == null)
				throw new ArgumentNullException("presentation");

			if (stream == null)
				throw new ArgumentNullException("stream");


			TotalSlides = presentation.Slides.Count;


			// A memory stream backs our document
			using (var ms = new MemoryStream(2048))
			{
				// a package contains all parts of the document
				Package package = Package.Open(ms, FileMode.Create, FileAccess.ReadWrite);

				PackageStore.AddPackage(PackageUri, package);

				// the document uses our package for storage
				using (XpsDocument xps_doc = new XpsDocument(package, CompressionOption.Normal, PackageUri.AbsoluteUri))
				{
					// TODO XPS Writer: Document haders (CoreDocumentProperties) does not works
					xps_doc.CoreDocumentProperties.Title = presentation.Title;
					xps_doc.CoreDocumentProperties.Creator = presentation.Author;

					// we use the writer to write the fixed document to the xps document
					XpsDocumentWriter writer = XpsDocument.CreateXpsDocumentWriter(xps_doc);

					// An xps document is one or more FixedDocuments containing FixedPages
					FixedDocument fdoc = new FixedDocument();

					//foreach (Slide slide in presentation.Slides)
					for (int i = 0; i < presentation.Slides.Count; i++)
					{
						var slide = presentation.Slides[i];

						FixedPage fpage = new FixedPage
						{
							Width = slide.Size.Width,
							Height = slide.Size.Height
						};

						var canvas = new InkCanvas // need to create new canvas, because of: Specified element is already the logical child of another element. Disconnect it first
						{
							Width = slide.Size.Width,
							Height = slide.Size.Height,
							Strokes = slide.Canvas.Strokes,
							Background = slide.Canvas.Background
						};

						// http://msdn.microsoft.com/en-us/library/system.windows.xps.packaging.ixpsfixedpagewriter.aspx
						//fpage.Resources.Add(STROKES_KEY, strokes);

						// HACK XPS Writer: Save strokes into TextBlock -> many Glyphs
						StrokeCollectionConverter sconv = new StrokeCollectionConverter();
						fpage.Children.Add(new TextBlock() { Text = sconv.ConvertToString(canvas.Strokes), Height = 0.1d });

						fpage.Children.Add(canvas);

						var pc = new PageContent();
						((IAddChild)pc).AddChild(fpage);
						fdoc.Pages.Add(pc);

						// Another slide is done
						SlidesDone = i + 1;
					}

					// The paginator controls page breaks during the writing process
					// its important since xps document content does not flow 
					writer.Write(fdoc.DocumentPaginator);
					package.Flush();

					// this part serializes the doc to a stream so we can get the bytes
					var swriter = new XpsSerializerFactory().CreateSerializerWriter(stream);
					swriter.Write(xps_doc.GetFixedDocumentSequence());
				}

				PackageStore.RemovePackage(PackageUri);

				package.Close();
			}
		}
		#endregion


		#region IReader Members
		/// <summary>
		/// Reads the specified stream and tries to create new presentation from it.
		/// </summary>
		/// <param name="config">The configuration object.</param>
		/// <param name="stream">The stream to read from.</param>
		/// <param name="presentation">The presentation.</param>
		/// <exception cref="ThePresenter.ReadException"/>
		public void Read(DependencyObject config, Stream stream, out Presentation presentation)
		{
			presentation = null;

			if (stream == null)
				throw new ArgumentNullException("stream");


			using (Package package = Package.Open(stream, FileMode.Open, FileAccess.Read))
			{
				PackageStore.AddPackage(PackageUri, package);

				XpsDocument xps_doc = new XpsDocument(package) { Uri = PackageUri };

				presentation = new Presentation
				{
					Author = xps_doc.CoreDocumentProperties.Creator,
					Title = xps_doc.CoreDocumentProperties.Title
				};

				FixedDocumentSequence seq = xps_doc.GetFixedDocumentSequence();
				const bool bForceReload = false;

				foreach (var doc_ref in seq.References) // should be only one
				{
					// get the FixedDocument object from this reference
					FixedDocument fdoc = doc_ref.GetDocument(bForceReload);

					TotalSlides = fdoc.Pages.Count;

					// get the pages referenced by this fixed document
					//foreach (PageContent page_cont in fdoc.Pages)
					for (int i = 0; i < fdoc.Pages.Count; i++)
					{
						var page_cont = fdoc.Pages[i];

						FixedPage fpage = page_cont.GetPageRoot(bForceReload);
						// TODO XPS format use rendered image (in case of files not from ThePresenter)

						var slide = new Slide
						{
							Size = new Size(fpage.Width, fpage.Height),
							Canvas = new InkCanvas()
							{
								Background = fpage.Background,
								Strokes = new StrokeCollection()
							}
						};

						if (fpage.Resources.Contains(BGIMG_KEY) && fpage.Resources[BGIMG_KEY] is ImageBrush)
						{
							slide.Canvas.Background = (ImageBrush)fpage.Resources[BGIMG_KEY];
						}

						var sb = new StringBuilder();

						// in each page, browse the list of elements contained in this page 
						foreach (UIElement elem in fpage.Children)
						{
							if (elem is Canvas)
							{
								foreach (UIElement item in (elem as Canvas).Children)
								{
									if (item is Glyphs)
									{
										var text = (item as Glyphs).UnicodeString;

										if (!string.IsNullOrEmpty(text))
										{
											sb.Append(text);
										}
									}
								}
							}
						} // next UI Element

						var conv = new StrokeCollectionConverter();

						try
						{
							slide.Canvas.Strokes = (StrokeCollection)conv.ConvertFromString(sb.ToString());
						}
						catch (FormatException)
						{
							// TODO Implement some test, if this XPS file is saved from ThePresenter
							// string is not an encoded base64 string
						}

						presentation.Slides.Add(slide);

						// Another slide is done
						SlidesDone = i + 1;
					} // next FixedPage part
				} // next Document in this Set of references 

				PackageStore.RemovePackage(PackageUri);
			}
		}
		#endregion
	}
}
